--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 24edabb9ab2532f676cb92d63aba640fed9dfeee
Parents : 41e40dc
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-05-10T01:53:51-05:00
fix(nomadnet): parse relative /page/ and /file/ URLs, show full URL in link hover titles
Changes
3 files changed, 39 insertions(+), 10 deletions(-)
Diff
diff --git a/meshchatx/src/frontend/components/blocked/BlockedPage.vue b/meshchatx/src/frontend/components/blocked/BlockedPage.vue
index 8777fa59..f8d9ac9c 100644
--- a/meshchatx/src/frontend/components/blocked/BlockedPage.vue
+++ b/meshchatx/src/frontend/components/blocked/BlockedPage.vue
@@ -43,7 +43,10 @@
</div>
<div class="flex-1 overflow-y-auto p-4 md:p-6">
- <div v-if="isLoading && filteredBlockedIdentities.length === 0" class="flex flex-col items-center justify-center h-64">
+ <div
+ v-if="isLoading && filteredBlockedIdentities.length === 0"
+ class="flex flex-col items-center justify-center h-64"
+ >
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
<p class="text-gray-500 dark:text-gray-400">{{ $t("banishment.loading_items") }}</p>
</div>
@@ -126,14 +129,20 @@
class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 font-mono bg-gray-50 dark:bg-zinc-800 px-2 py-1 rounded"
>
<span class="break-all">{{ dest.destination_hash }}</span>
- <span v-if="dest.created_at" class="shrink-0 ml-2 text-gray-400 dark:text-zinc-500">
+ <span
+ v-if="dest.created_at"
+ class="shrink-0 ml-2 text-gray-400 dark:text-zinc-500"
+ >
{{ formatTimeAgo(dest.created_at) }}
</span>
</div>
</div>
</div>
- <div v-if="identity.rns_reason" class="text-xs italic text-zinc-500 dark:text-zinc-400 mb-2">
+ <div
+ v-if="identity.rns_reason"
+ class="text-xs italic text-zinc-500 dark:text-zinc-400 mb-2"
+ >
"{{ identity.rns_reason }}"
</div>
<div
@@ -192,9 +201,7 @@ export default {
return this.allBlockedIdentities.filter((identity) => {
if (identity.identity_hash.toLowerCase().includes(query)) return true;
if ((identity.display_name || "").toLowerCase().includes(query)) return true;
- return identity.blocked_destinations.some((d) =>
- d.destination_hash.toLowerCase().includes(query)
- );
+ return identity.blocked_destinations.some((d) => d.destination_hash.toLowerCase().includes(query));
});
},
},
@@ -313,7 +320,9 @@ export default {
async onUnblock(identity) {
if (
!(await DialogUtils.confirm(
- this.$t("banishment.lift_banishment_confirm", { name: identity.display_name || identity.identity_hash })
+ this.$t("banishment.lift_banishment_confirm", {
+ name: identity.display_name || identity.identity_hash,
+ })
))
) {
return;
@@ -321,9 +330,10 @@ export default {
try {
// Use the first blocked destination hash, or fall back to identity hash
- const targetHash = identity.blocked_destinations.length > 0
- ? identity.blocked_destinations[0].destination_hash
- : identity.identity_hash;
+ const targetHash =
+ identity.blocked_destinations.length > 0
+ ? identity.blocked_destinations[0].destination_hash
+ : identity.identity_hash;
await window.api.delete(`/api/v1/blocked-destinations/${targetHash}`);
await this.loadBlockedDestinations();
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
index 39c42c2e..9cb185b5 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
@@ -1962,6 +1962,16 @@ export default {
}
}
+ // parse relative page/file urls (e.g. /file/artifact`g=reticulum|r=lxmf)
+ if (url.startsWith("/page/") || url.startsWith("/file/")) {
+ const queryIndex = url.indexOf("?");
+ return {
+ destination_hash: null,
+ path: queryIndex >= 0 ? url.substring(0, queryIndex) : url,
+ query: queryIndex >= 0 ? url.substring(queryIndex + 1) : null,
+ };
+ }
+
// parse node id only
if (url.length === 32) {
return {
diff --git a/meshchatx/src/frontend/js/NomadPageRenderer.js b/meshchatx/src/frontend/js/NomadPageRenderer.js
index 11d8f365..5493eb97 100644
--- a/meshchatx/src/frontend/js/NomadPageRenderer.js
+++ b/meshchatx/src/frontend/js/NomadPageRenderer.js
@@ -160,6 +160,15 @@ export function isolateNomadLinksInHtml(html, destinationHash) {
a.classList.add("nomadnet-link", "text-blue-600", "dark:text-blue-400", "hover:underline");
} else {
a.setAttribute("href", "#");
+ // For micron parser links with data-destination, update title so hover shows the full URL
+ const dataDest = a.getAttribute("data-destination");
+ if (dataDest) {
+ let titleUrl = dataDest.trim();
+ if (!/^[a-f0-9]{32}:/i.test(titleUrl)) {
+ titleUrl = `${dh}:${titleUrl.startsWith(":") ? titleUrl.slice(1) : titleUrl}`;
+ }
+ a.setAttribute("title", titleUrl);
+ }
}
a.removeAttribute("target");
a.removeAttribute("rel");
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────